package com.javabi.common.lang;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Provides a way to call exit on an application from any number of threads, without any calling thread blocking.
 */
public class SystemExit implements Runnable {

	private static final AtomicReference<Integer> exitStatus = new AtomicReference<Integer>(null);
	private static final AtomicReference<Throwable> exitCause = new AtomicReference<Throwable>(null);

	public static final Integer getExitStatus() {
		// Returns null if not exiting ...
		return exitStatus.get();
	}

	public static final Throwable getExitCause() {
		// Returns null if no cause set (but could still have exited)
		return exitCause.get();
	}

	public static final boolean hasCrashed() {
		Integer status = exitStatus.get();
		return status != null && status.intValue() != 0;
	}

	/**
	 * Call to invoke System.exit without blocking.
	 * @param status the status.
	 * @return true if invoked, false if already invoked.
	 */
	public static final boolean exit(int status) {
		return exit(status, null);
	}

	/**
	 * Call to invoke System.exit without blocking.
	 * @param status the status.
	 * @param cause the cause of the exit (will only be set if successfully invoked).
	 * @return true if invoked, false if already invoked.
	 */
	public static final boolean exit(int status, Throwable cause) {
		if (cause != null) {
			cause.printStackTrace();
		}

		// Guarantees that System.exit() is called only once
		// and that it is called asynchronously
		if (exitStatus.compareAndSet(null, status)) {
			exitCause.set(cause);
			String name = "System.exit(" + status + ")";
			Thread thread = new Thread(new SystemExit(status), name);
			thread.setDaemon(true);
			thread.start();
			return true;
		}

		return false;
	}

	private final int status;

	private SystemExit(int status) {
		this.status = status;
	}

	@Override
	public void run() {
		Runtime.getRuntime().exit(status);
	}

}
